package com.gfl.trivialrestclient.dao.hibernate;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import com.gfl.trivialrestclient.dao.GenericDao;

/**
 * Hibernate Dao with parametrisation. Automatic type detection assumes that
 * entity class is first parameter and isn't nested generic declaration.
 * 
 * @author Bedlam
 */
@Component
abstract class GenericDaoHibernate<E, PK extends Serializable> implements
		GenericDao<E, PK> {

	protected final Log log = LogFactory.getLog(getClass());
	private final Class<E> entityClass;
	private final String COUNT_QUERY;
	private final String FIND_ALL_QUERY;
	private SessionFactory sessionFactory;

	public GenericDaoHibernate(Class<E> entityClass) {
		this.entityClass = entityClass;

		COUNT_QUERY = "select count(e) from " + entityClass.getName() + " e";
		FIND_ALL_QUERY = "select e from " + entityClass.getName() + " e";
	}

	@SuppressWarnings("unchecked")
	public GenericDaoHibernate() {
		Type t = getClass().getGenericSuperclass();
		ParameterizedType pt = (ParameterizedType) t;
		entityClass = (Class<E>) pt.getActualTypeArguments()[0];

		COUNT_QUERY = "select count(e) from " + entityClass.getName() + " e";
		FIND_ALL_QUERY = "select e from " + entityClass.getName() + " e";
	}

	@Autowired
	public void setSessionFactory(SessionFactory sessionFactory) {
		this.sessionFactory = sessionFactory;
	}

	public Class<E> getEntityClass() {
		return entityClass;
	}

	public SessionFactory getSessionFactory() {
		return sessionFactory;
	}

	@Transactional
	@Override
	public E save(E newInstance) {
		getSessionFactory().getCurrentSession().persist(newInstance);
		return newInstance;
	}

	@SuppressWarnings("unchecked")
	@Transactional
	@Override
	public E edit(E entity) {
		E persisted = (E) getSessionFactory().getCurrentSession().merge(entity);
		return persisted;
	}

	@Transactional
	@Override
	public void delete(E persistentObject) {
		getSessionFactory().getCurrentSession().delete(persistentObject);
	}

	@SuppressWarnings("unchecked")
	@Transactional(readOnly = true)
	@Override
	public E findById(PK pk) {
		return (E) getSessionFactory().getCurrentSession().get(
				getEntityClass(), pk);
	}

	@SuppressWarnings("unchecked")
	@Transactional(readOnly = true)
	@Override
	public List<E> findAll() {
		return (List<E>) getSessionFactory().getCurrentSession()
				.createQuery(FIND_ALL_QUERY).list();
	}

	@Transactional(readOnly = true)
	@Override
	public long countAll() {
		return ((Long) getSessionFactory().getCurrentSession()
				.createQuery(COUNT_QUERY).uniqueResult()).longValue();
	}
}
